﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Tycho.Runtime;

namespace Tycho.Persistence {
    public class ObjectSerializer {
        public class ReferencedSerialisedObject {
            public Guid Id { get; set; }
            public SerializedObject Object { get; set; }
        };

        public List<ReferencedSerialisedObject> ReferencedObjects { get; private set; }
        Dictionary<AnyObject, SerializedObject> SerializedObjects;
        IdentityMap IdentityMap;

        public ObjectSerializer (IdentityMap identityMap) {
            ReferencedObjects = new List<ReferencedSerialisedObject> ();
            SerializedObjects = new Dictionary<AnyObject, SerializedObject> ();
            IdentityMap = identityMap;
        }

        public SerializedObject Serialize (AnyObject obj) {
            SerializedObject alreadySerialized;

            if (SerializedObjects.TryGetValue (obj, out alreadySerialized)) {
                return alreadySerialized;
            } else {
                AnyObject partiallySerialized = obj.ActuallySerialize ();

                Guid id = IdentityMap.GetReference (obj);
                SerializedObject serialized = ReallySerialize (obj, partiallySerialized);
                ReferencedObjects.Add (new ReferencedSerialisedObject { Object = serialized, Id = id });

                return serialized;
            }
        }

        SerializedObject StoreSerializedObject (AnyObject obj, SerializedObject sero) {
            if (obj != null) {
                SerializedObjects.Add (obj, sero);
            }

            return sero;
        }

        SerializedObject ReallySerialize (AnyObject partiallySerialized) {
            return ReallySerialize (null, partiallySerialized);
        }

        SerializedObject ReallySerialize (AnyObject obj, AnyObject partiallySerialized) {
            if (partiallySerialized is ListObject) {
                ListObject list = partiallySerialized as ListObject;

                SerializedList sero = new SerializedList ();
                StoreSerializedObject (obj, sero);

                sero.Items.AddRange (from item in list.Items select ReallySerialize (item));

                return sero;
            } else if (partiallySerialized is StructureObject) {
                StructureObject str = partiallySerialized as StructureObject;
                SerializedStructure sero = new SerializedStructure ();
                StoreSerializedObject (obj, sero);

                foreach (KeyValuePair<AnyObject, AnyObject> field in str.Fields) {
                    sero.Fields.Add (new SerializedStructure.Field ((SerializedSymbol) ReallySerialize (field.Key.Expect<Symbol> ()), ReallySerialize (field.Value)));
                }

                return sero;
            } else if (partiallySerialized is ReferenceObject) {
                AnyObject reference = (partiallySerialized as ReferenceObject).Target;

                Guid id = IdentityMap.GetReference (reference);
                SerializedReference r = new SerializedReference (id);

                SerializedObject sero = Serialize (reference);

                return r;
            } else if (partiallySerialized is Symbol) {
                return new SerializedSymbol (partiallySerialized.ToString ());
            } else if (partiallySerialized is IntegerObject) {
                return new SerializedInteger ((partiallySerialized as IntegerObject).Value);
            } else if (partiallySerialized is RealObject) {
                return new SerializedReal ((partiallySerialized as RealObject).Value);
            } else if (partiallySerialized is StringObject) {
                return new SerializedString ((partiallySerialized as StringObject).Value);
            } else if (partiallySerialized is BooleanObject) {
                return new SerializedBoolean ((partiallySerialized as BooleanObject).Value);
            } else if (partiallySerialized is DateTimeObject) {
                return new SerializedDateTime ((partiallySerialized as DateTimeObject).Value);
            } else if (partiallySerialized is NullObject) {
                return new SerializedNull ();
            } else {
                throw new TychoException ("could not serialize object " + obj);
            }
        }
    }
}
